// Copyright (c) 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 or MIT

use serde::Deserialize;
use std::assert;
use std::env;
use std::io::Write;
use std::path::Path;
use std::{fs, fs::File};

#[derive(Debug, PartialEq, Deserialize)]
struct SpdmConfig {
    cert_config: SpdmCertConfig,
    measurement_config: SpdmMeasurementConfig,
    psk_config: SpdmPskConfig,
    max_opaque_list_elements_count: usize,
    max_session_count: usize,
    transport_config: SpdmBufferConfig,
    max_spdm_msg_size: usize,
    heartbeat_period_value: u8,
    max_root_cert_support: usize,
}

impl SpdmConfig {
    fn validate_content(&self) {
        // All rust fixed-size arrays require non-negative compile-time constant sizes.
        // This will be checked by the compiler thus no need to check again here.

        // We dont support chunking now.
        assert!(self.max_spdm_msg_size >= 42);

        // Reserve some space for transport overhead.
        // 24 is miniaml requirement: session_id (4) + len (2) + app_len (2) + mac (16)
        assert!(self.transport_config.receiver_buffer_size > self.max_spdm_msg_size + 24);
        assert!(self.transport_config.sender_buffer_size > self.max_spdm_msg_size + 24);

        assert!(self.cert_config.max_cert_chain_data_size <= 0xFFFF);
        // no need to check max_cert_chain_data_size against max_spdm_msg_size

        assert!(self.measurement_config.max_measurement_record_size <= 0xFFFFFF);
        assert!(self.measurement_config.max_measurement_val_len <= 0xFFFF - 7);
        assert!(
            self.measurement_config.max_measurement_record_size
                >= 7 + self.measurement_config.max_measurement_val_len
        );
        assert!(self.measurement_config.max_measurement_val_len >= 32);
        assert!(self.measurement_config.max_measurement_record_size < self.max_spdm_msg_size);

        assert!(self.psk_config.max_psk_context_size >= 32);
        assert!(self.psk_config.max_psk_context_size <= 0xFFFF);
        assert!(self.psk_config.max_psk_hint_size <= 0xFFFF);
        assert!(
            self.psk_config.max_psk_context_size + self.psk_config.max_psk_hint_size
                < self.max_spdm_msg_size
        );

        // TODO: add more sanity checks if needed.
    }
}

#[derive(Debug, PartialEq, Deserialize)]
struct SpdmCertConfig {
    max_cert_chain_data_size: usize,
}

#[derive(Debug, PartialEq, Deserialize)]
struct SpdmMeasurementConfig {
    max_measurement_record_size: usize,
    max_measurement_val_len: usize,
}

#[derive(Debug, PartialEq, Deserialize)]
struct SpdmPskConfig {
    max_psk_context_size: usize,
    max_psk_hint_size: usize,
}

#[derive(Debug, PartialEq, Deserialize)]
struct SpdmBufferConfig {
    sender_buffer_size: usize,
    receiver_buffer_size: usize,
}

macro_rules! TEMPLATE {
    () => {
"// Copyright (c) 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 or MIT
//
// Automatically generated by build scripts.
// It is not intended for manual editing.
// Please kindly configure via etc/config.json instead.

/// This is used in SpdmCertChainData without SpdmCertChainHeader.
pub const MAX_SPDM_CERT_CHAIN_DATA_SIZE: usize = {cert_chain_data_sz}; // 0x1000;

/// This is used in SpdmMeasurementsResponsePayload
pub const MAX_SPDM_MEASUREMENT_RECORD_SIZE: usize = {meas_rec_sz}; // 0x1000

/// This is used in SpdmDmtfMeasurementStructure <- SpdmMeasurementBlockStructure <- SpdmMeasurementsResponsePayload
/// It should be MAX (MAX MEASUREMENT_MANIFEST_LEN, MAX supported DIGEST SIZE)
pub const MAX_SPDM_MEASUREMENT_VALUE_LEN: usize = {meas_val_len}; // 0x400

/// This is used in SpdmPskExchangeRequestPayload / SpdmPskExchangeResponsePayload
/// It should be no smaller than negoatiated DIGEST SIZE.
pub const MAX_SPDM_PSK_CONTEXT_SIZE: usize = {psk_ctx_sz};

/// This is used in SpdmPskExchangeRequestPayload / SpdmPskExchangeResponsePayload
pub const MAX_SPDM_PSK_HINT_SIZE: usize = {psk_hint_sz};

/// This is used in Key exchange opaque data
pub const MAX_OPAQUE_LIST_ELEMENTS_COUNT: usize = {max_opaque_list_elements_cnt};

/// This is used in SpdmContext
pub const MAX_SPDM_SESSION_COUNT: usize = {session_cnt};

/// This is sender buffer for SPDM transport layer (e.g. MCTP or PCI_DOE)
/// It is MAX_SPDM_MSG_SIZE + transport overhead (plain text or cipher text, head and tail)
/// It is also used as app buffer (bigger than MAX_SPDM_MSG_SIZE)
pub const SENDER_BUFFER_SIZE: usize = {snd_buf_sz};

/// This is receiver buffer for transport layer (e.g. MCTP or PCI_DOE)
/// It is MAX_SPDM_MSG_SIZE + transport overhead (plain text or cipher text, head and tail)
/// It is also used as app buffer (bigger than MAX_SPDM_MSG_SIZE)
pub const RECEIVER_BUFFER_SIZE: usize = {rcv_buf_sz};

/// Required sender/receiver buffer for transport layer
/// +-------+--------+---------------------------+------+--+------+---+--------+-----+
/// | TYPE  |TransHdr|      EncryptionHeader     |AppHdr|  |Random|MAC|AlignPad|FINAL|
/// |       |        |SessionId|SeqNum|Len|AppLen|      |  |      |   |        |     |
/// +-------+--------+---------------------------+------+  +------+---+--------+-----+
/// | MCTP  |    1   |    4    |   2  | 2 |   2  |   1  |  |  32  | 16|   0    |  60 |
/// +-------+--------+---------------------------+------+--+------+---+--------+-----+
///
pub const MCTP_TRANSPORT_ADDITIONAL_SIZE: usize = 60;

/// Required sender/receiver buffer for transport layer
/// +-------+--------+---------------------------+------+--+------+---+--------+-----+
/// | TYPE  |TransHdr|      EncryptionHeader     |AppHdr|  |Random|MAC|AlignPad|FINAL|
/// |       |        |SessionId|SeqNum|Len|AppLen|      |  |      |   |        |     |
/// +-------+--------+---------------------------+------+  +------+---+--------+-----+
/// |PCI_DOE|    8   |    4    |   0  | 2 |   2  |   0  |  |   0  | 16|   3    |  35 |
/// +-------+--------+---------------------------+------+--+------+---+--------+-----+
///
pub const PCI_DOE_TRANSPORT_ADDITIONAL_SIZE: usize = 35;

/// This is max individual SPDM message size defined in SPDM 1.2.
pub const MAX_SPDM_MSG_SIZE: usize = {max_spdm_mgs_sz};

/// This is used by responder to specify the heartbeat period
/// 0 represents either Heartbeat is not supported or
/// heartbeat is not desired on a session
pub const HEARTBEAT_PERIOD: u8 = {heartbeat_period};

/// This is used for SpdmProvisionInfo.peer_root_cert_data
pub const MAX_ROOT_CERT_SUPPORT: usize = {max_root_cert_supported};
"
};
}

const SPDM_CONFIG_ENV: &str = "SPDM_CONFIG";
const SPDM_CONFIG_JSON_DEFAULT_PATH: &str = "etc/config.json";
const SPDM_CONFIG_RS_OUT_DIR: &str = "src";
const SPDM_CONFIG_RS_OUT_FILE_NAME: &str = "config.rs";

fn main() {
    // Read and parse the SPDM configuration file.
    let spdm_config_json_file_path =
        env::var(SPDM_CONFIG_ENV).unwrap_or_else(|_| SPDM_CONFIG_JSON_DEFAULT_PATH.to_string());
    let spdm_config_json_file =
        File::open(spdm_config_json_file_path).expect("The SPDM configuration file does not exist");
    let spdm_config: SpdmConfig = serde_json::from_reader(spdm_config_json_file)
        .expect("It is not a valid SPDM configuration file.");

    // Do sanity checks.
    spdm_config.validate_content();

    // Generate config .rs file from the template and JSON inputs, then write to fs.
    let mut to_generate = Vec::new();
    write!(
        &mut to_generate,
        TEMPLATE!(),
        cert_chain_data_sz = spdm_config.cert_config.max_cert_chain_data_size,
        meas_rec_sz = spdm_config.measurement_config.max_measurement_record_size,
        meas_val_len = spdm_config.measurement_config.max_measurement_val_len,
        psk_ctx_sz = spdm_config.psk_config.max_psk_context_size,
        psk_hint_sz = spdm_config.psk_config.max_psk_hint_size,
        max_opaque_list_elements_cnt = spdm_config.max_opaque_list_elements_count,
        session_cnt = spdm_config.max_session_count,
        snd_buf_sz = spdm_config.transport_config.sender_buffer_size,
        rcv_buf_sz = spdm_config.transport_config.receiver_buffer_size,
        max_spdm_mgs_sz = spdm_config.max_spdm_msg_size,
        heartbeat_period = spdm_config.heartbeat_period_value,
        max_root_cert_supported = spdm_config.max_root_cert_support,
    )
    .expect("Failed to generate configuration code from the template and JSON config");

    let dest_path = Path::new(SPDM_CONFIG_RS_OUT_DIR).join(SPDM_CONFIG_RS_OUT_FILE_NAME);
    fs::write(dest_path, to_generate).unwrap();

    // Re-run the build script if the files at the given paths or envs have changed.
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=../Cargo.lock");
    println!("cargo:rerun-if-changed={}", SPDM_CONFIG_JSON_DEFAULT_PATH);
    println!("cargo:rerun-if-env-changed={}", SPDM_CONFIG_ENV);
}
